/*
 * Copyright 2018 Rami Jemli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http:// www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ramijemli.percentagechartview.renderer;

import com.ramijemli.percentagechartview.AttrUtils;
import com.ramijemli.percentagechartview.IPercentageChartView;
import com.ramijemli.percentagechartview.Utils;
import com.ramijemli.percentagechartview.ValueAnimator;
import com.ramijemli.percentagechartview.annotation.ProgressOrientation;
import com.ramijemli.percentagechartview.callback.AdaptiveColorProvider;
import com.ramijemli.percentagechartview.callback.ProgressTextFormatter;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbPalette;
import ohos.agp.components.AttrSet;
import ohos.agp.render.BlurDrawLooper;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Shader;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Base mode renderer
 */
public abstract class BaseModeRenderer {
    /**
     * * The constant NORMAL
     */
    public static final int NORMAL = 0;
    /**
     * * The constant BOLD
     */
    public static final int BOLD = 1;
    /**
     * * The constant ITALIC
     */
    public static final int ITALIC = 2;
    /**
     * * The constant BOLD_ITALIC
     */
    public static final int BOLD_ITALIC = 3;
    /**
     * * The constant MODE_RING
     */
    public static final int MODE_RING = 0;
    /**
     * * The constant MODE_PIE
     */
    public static final int MODE_PIE = 1;
    /**
     * * The constant MODE_FILL
     */
    public static final int MODE_FILL = 2;

    /**
     * * The constant INVALID_ORIENTATION
     */
    public static final int INVALID_ORIENTATION = -1;
    /**
     * * The constant ORIENTATION_CLOCKWISE
     */
    public static final int ORIENTATION_CLOCKWISE = 0;
    /**
     * * The constant ORIENTATION_COUNTERCLOCKWISE
     */
    public static final int ORIENTATION_COUNTERCLOCKWISE = 1;

    /**
     * * The constant INVALID_GRADIENT
     */
    public static final int INVALID_GRADIENT = -1;
    /**
     * * The constant GRADIENT_LINEAR
     */
    public static final int GRADIENT_LINEAR = 0;
    /**
     * * The constant GRADIENT_RADIAL
     */
    public static final int GRADIENT_RADIAL = 1;
    /**
     * * The constant GRADIENT_SWEEP
     */
    public static final int GRADIENT_SWEEP = 2;
    /**
     * * The constant LINEAR
     */
    public static final int LINEAR = 0;
    /**
     * * The constant ACCELERATE
     */
    public static final int ACCELERATE = 1;
    /**
     * * The constant DECELERATE
     */
    public static final int DECELERATE = 2;
    /**
     * * The constant ACCELERATE_DECELERATE
     */
    public static final int ACCELERATE_DECELERATE = 3;
    /**
     * * The constant ANTICIPATE
     */
    public static final int ANTICIPATE = 4;
    /**
     * * The constant OVERSHOOT
     */
    public static final int OVERSHOOT = 5;
    /**
     * * The constant ANTICIPATE_OVERSHOOT
     */
    public static final int ANTICIPATE_OVERSHOOT = 6;
    /**
     * * The constant BOUNCE
     */
    public static final int BOUNCE = 7;
    /**
     * * The constant FAST_OUT_LINEAR_IN
     */
    public static final int FAST_OUT_LINEAR_IN = 8;
    /**
     * * The constant FAST_OUT_SLOW_IN
     */
    public static final int FAST_OUT_SLOW_IN = 9;
    /**
     * * The constant LINEAR_OUT_SLOW_IN
     */
    public static final int LINEAR_OUT_SLOW_IN = 10;
    /**
     * * The constant PercentageChartView_pcv_orientation
     */
    public static final String PERCENTAGECHARTVIEW_PCV_ORIENTATION = "pcv_orientation";
    /**
     * * The constant PercentageChartView_pcv_mode
     */
    public static final String PERCENTAGECHARTVIEW_PCV_MODE = "pcv_mode";
    /**
     * * The constant PercentageChartView_pcv_startAngle
     */
    public static final String PERCENTAGECHARTVIEW_PCV_STARTANGLE = "pcv_startAngle";
    /**
     * * The constant PercentageChartView_pcv_drawBackground
     */
    public static final String PERCENTAGECHARTVIEW_PCV_DRAWBACKGROUND = "pcv_drawBackground";
    /**
     * * The constant PercentageChartView_pcv_backgroundColor
     */
    public static final String PERCENTAGECHARTVIEW_PCV_BACKGROUNDCOLOR = "pcv_backgroundColor";
    /**
     * * The constant PercentageChartView_pcv_progress
     */
    public static final String PERCENTAGECHARTVIEW_PCV_PROGRESS = "pcv_progress";
    /**
     * * The constant PercentageChartView_pcv_progressColor
     */
    public static final String PERCENTAGECHARTVIEW_PCV_PROGRESSCOLOR = "pcv_progressColor";
    /**
     * * The constant PercentageChartView_pcv_gradientType
     */
    public static final String PERCENTAGECHARTVIEW_PCV_GRADIENTTYPE = "pcv_gradientType";
    /**
     * * The constant PercentageChartView_pcv_gradientColors
     */
    public static final String PERCENTAGECHARTVIEW_PCV_GRADIENTCOLORS = "pcv_gradientColors";
    /**
     * * The constant PercentageChartView_pcv_gradientDistributions
     */
    public static final String PERCENTAGECHARTVIEW_PCV_GRADIENTDISTRIBUTIONS = "pcv_gradientDistributions";
    /**
     * * The constant PercentageChartView_pcv_gradientAngle
     */
    public static final String PERCENTAGECHARTVIEW_PCV_GRADIENTANGLE = "pcv_gradientAngle";
    /**
     * * The constant PercentageChartView_pcv_animDuration
     */
    public static final String PERCENTAGECHARTVIEW_PCV_ANIMDURATION = "pcv_animDuration";
    /**
     * * The constant PercentageChartView_pcv_animInterpolator
     */
    public static final String PERCENTAGECHARTVIEW_PCV_ANIMINTERPOLATOR = "pcv_animInterpolator";
    /**
     * * The constant PercentageChartView_pcv_textColor
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTCOLOR = "pcv_textColor";
    /**
     * * The constant PercentageChartView_pcv_textSize
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSIZE = "pcv_textSize";
    /**
     * * The constant PercentageChartView_pcv_typeface
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TYPEFACE = "pcv_typeface";
    /**
     * * The constant PercentageChartView_pcv_textStyle
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSTYLE = "pcv_textStyle";
    /**
     * * The constant PercentageChartView_pcv_textShadowRadius
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSHADOWRADIUS = "pcv_textShadowRadius";
    /**
     * * The constant PercentageChartView_pcv_textShadowDistY
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSHADOWDISTY = "pcv_textShadowDistY";
    /**
     * * The constant PercentageChartView_pcv_textShadowDistX
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSHADOWDISTX = "pcv_textShadowDistX";
    /**
     * * The constant PercentageChartView_pcv_textShadowColor
     */
    public static final String PERCENTAGECHARTVIEW_PCV_TEXTSHADOWCOLOR = "pcv_textShadowColor";
    /**
     * * The constant PercentageChartView_pcv_backgroundOffset
     */
    public static final String PERCENTAGECHARTVIEW_PCV_BACKGROUNDOFFSET = "pcv_backgroundOffset";
    /**
     * The constant Default max
     */
    static final float DEFAULT_MAX = 100;
    /**
     * * The constant DEFAULT_TEXT_SP_SIZE
     */
    private static final float DEFAULT_TEXT_SP_SIZE = 12;
    /**
     * * The constant DEFAULT_ANIMATION_INTERPOLATOR
     */
    private static final int DEFAULT_ANIMATION_INTERPOLATOR = 0;
    /**
     * * The constant DEFAULT_START_ANGLE
     */
    private static final int DEFAULT_START_ANGLE = 0;
    /**
     * * The constant DEFAULT_ANIMATION_DURATION
     */
    private static final int DEFAULT_ANIMATION_DURATION = 400;
    /**
     * The constant M anim interpolator
     */
    private final int mAnimInterpolator;
    /**
     * The constant M draw background
     */
    boolean mDrawBackground;
    /**
     * The constant M background paint
     */
    Paint mBackgroundPaint;
    /**
     * The constant M background color
     */
    int mBackgroundColor;
    /**
     * The constant M background offset
     */
    int mBackgroundOffset;
    /**
     * The constant M progress paint
     */
    Paint mProgressPaint;
    /**
     * The constant M progress color
     */
    int mProgressColor;
    /**
     * M gradient colors
     */
    Color[] mGradientColors;
    /**
     * M gradient distributions
     */
    float[] mGradientDistributions;
    /**
     * The constant M gradient type
     */
    int mGradientType;
    /**
     * The constant M gradient angle
     */
    float mGradientAngle;
    /**
     * The constant M gradient shader
     */
    Shader mGradientShader;
    /**
     * The constant M text paint
     */
    Paint mTextPaint;
    /**
     * The constant M text color
     */
    int mTextColor;
    /**
     * The constant M background bounds COMMON
     */
    RectFloat mBackgroundBounds;
    /**
     * The constant M circle bounds
     */
    RectFloat mCircleBounds;
    /**
     * The constant M progress color animator
     */
    ValueAnimator mProgressColorAnimator;
    /**
     * M background color animator
     */
    ValueAnimator mBackgroundColorAnimator;
    /**
     * M text color animator
     */
    ValueAnimator mTextColorAnimator;
    /**
     * M bg bar color animator
     */
    ValueAnimator mBgBarColorAnimator;
    /**
     * The constant M anim duration
     */
    int mAnimDuration;
    /**
     * The constant M progress
     */
    float mProgress;
    /**
     * The constant M start angle
     */
    float mStartAngle;
    /**
     * The constant M sweep angle
     */
    float mSweepAngle;
    /**
     * The constant Orientation
     */
    @ProgressOrientation
    int orientation;
    /**
     * The constant M adaptive color provider
     */
    AdaptiveColorProvider mAdaptiveColorProvider;
    /**
     * The constant M view
     */
    IPercentageChartView mView;
    /**
     * The constant M provided background color
     */
    private int mProvidedBackgroundColor;
    /**
     * The constant M provided text color
     */
    private int mProvidedTextColor;
    /**
     * The constant M text progress
     */
    private int mTextProgress;
    /**
     * The constant M text size
     */
    private float mTextSize;
    /**
     * The constant M text style
     */
    private int mTextStyle;
    /**
     * The constant M typeface
     */
    private Font.Builder mTypeface;
    /**
     * The constant M text shadow color
     */
    private int mTextShadowColor;
    /**
     * The constant M text shadow radius
     */
    private float mTextShadowRadius;
    /**
     * The constant M text shadow dist y
     */
    private float mTextShadowDistY;
    /**
     * The constant M text shadow dist x
     */
    private float mTextShadowDistX;
    /**
     * The constant M text editor
     */
    private String mTextEditor;
    /**
     * The constant M text layout
     */
    private String mTextLayout;
    /**
     * The constant M progress animator
     */
    private ValueAnimator mProgressAnimator;
    /**
     * The constant M provided progress color
     */
    private int mProvidedProgressColor;
    /**
     * The constant M provided text formatter
     */
    private ProgressTextFormatter mProvidedTextFormatter;
    /**
     * Default text formatter
     */
    private ProgressTextFormatter defaultTextFormatter;


    /**
     * Base mode renderer
     *
     * @param view view
     */
    BaseModeRenderer(IPercentageChartView view) {
        mView = view;

        // DRAWING ORIENTATION
        orientation = ORIENTATION_CLOCKWISE;

        // START DRAWING ANGLE
        mStartAngle = DEFAULT_START_ANGLE;

        // BACKGROUND DRAW STATE
        mDrawBackground = this instanceof PieModeRenderer;

        // BACKGROUND COLOR
        mBackgroundColor = Color.BLACK.getValue();

        // PROGRESS
        mProgress = mTextProgress = 0;

        // PROGRESS COLOR
        mProgressColor = Color.RED.getValue();

        // GRADIENT COLORS
        mGradientType = -1;
        mGradientAngle = (int) mStartAngle;

        // PROGRESS ANIMATION DURATION
        mAnimDuration = DEFAULT_ANIMATION_DURATION;

        // PROGRESS ANIMATION INTERPOLATOR
        mAnimInterpolator = Animator.CurveType.LINEAR;

        // TEXT COLOR
        mTextColor = Color.WHITE.getValue();

        // TEXT SIZE
        mTextSize = Utils.dp2px(mView.getViewContext(), (int) DEFAULT_TEXT_SP_SIZE);

        // TEXT STYLE
        mTextStyle = NORMAL;

        // TEXT SHADOW
        mTextShadowColor = Color.TRANSPARENT.getValue();
        mTextShadowRadius = 0;
        mTextShadowDistX = 0;
        mTextShadowDistY = 0;

        // BACKGROUND OFFSET
        mBackgroundOffset = 0;
    }

    /**
     * Base mode renderer
     *
     * @param view  view
     * @param attrs attrs
     */
    BaseModeRenderer(IPercentageChartView view, AttrSet attrs) {
        mView = view;

        // DRAWING ORIENTATION
        orientation = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_ORIENTATION, ORIENTATION_CLOCKWISE);

        // START DRAWING ANGLE
        mStartAngle = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_STARTANGLE, DEFAULT_START_ANGLE);
        if (mStartAngle < 0 || mStartAngle > 360) {
            mStartAngle = DEFAULT_START_ANGLE;
        }

        // BACKGROUND DRAW STATE
        mDrawBackground = AttrUtils.getBooleanFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_DRAWBACKGROUND,
                (this instanceof PieModeRenderer || this instanceof FillModeRenderer));

        // BACKGROUND COLOR
        mBackgroundColor = AttrUtils.getColorFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_BACKGROUNDCOLOR, Color.BLACK.getValue());

        // PROGRESS
        mProgress = AttrUtils.getFloatFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_PROGRESS, 0);
        if (mProgress < 0) {
            mProgress = 0;
        } else if (mProgress > 100) {
            mProgress = 100;
        }
        mTextProgress = (int) mProgress;

        // PROGRESS COLOR
        mProgressColor = AttrUtils.getColorFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_PROGRESSCOLOR, Color.BLUE.getValue());

        // GRADIENT COLORS
        initGradientColors(attrs);

        // PROGRESS ANIMATION DURATION
        mAnimDuration = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_ANIMDURATION, DEFAULT_ANIMATION_DURATION);

        // PROGRESS ANIMATION INTERPOLATOR
        int interpolator = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_ANIMINTERPOLATOR, DEFAULT_ANIMATION_INTERPOLATOR);
        switch (interpolator) {
            default:
            case LINEAR:
                mAnimInterpolator = Animator.CurveType.LINEAR;
                break;

            case ACCELERATE:
                mAnimInterpolator = Animator.CurveType.ACCELERATE;
                break;
            case DECELERATE:
                mAnimInterpolator = Animator.CurveType.DECELERATE;
                break;
            case ACCELERATE_DECELERATE:
                mAnimInterpolator = Animator.CurveType.ACCELERATE_DECELERATE;
                break;
            case ANTICIPATE:
                mAnimInterpolator = Animator.CurveType.ANTICIPATE;
                break;
            case OVERSHOOT:
                mAnimInterpolator = Animator.CurveType.OVERSHOOT;
                break;
            case ANTICIPATE_OVERSHOOT:
                mAnimInterpolator = Animator.CurveType.ANTICIPATE_OVERSHOOT;
                break;
            case BOUNCE:
                mAnimInterpolator = Animator.CurveType.BOUNCE;
                break;
        }

        // TEXT COLOR
        mTextColor = AttrUtils.getColorFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTCOLOR, Color.WHITE.getValue());

        // TEXT SIZE
        mTextSize = AttrUtils.getDimensionFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSIZE,
                Utils.dp2px(mView.getViewContext(), (int) DEFAULT_TEXT_SP_SIZE));

        // TEXT TYPEFACE
        String typeface = AttrUtils.getStringFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TYPEFACE, null);
        if (typeface != null && !typeface.isEmpty()) {
            mTypeface = createFontBuild(mView.getViewContext(), typeface);
        } else {
            mTypeface = new Font.Builder("");
        }

        // TEXT STYLE
        mTextStyle = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSTYLE, NORMAL);

        switch (mTextStyle) {
            case NORMAL:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(false);
                break;
            case BOLD:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(false);
                break;
            case ITALIC:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(true);
                break;
            case BOLD_ITALIC:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(true);
                break;
        }
        // TEXT SHADOW
        mTextShadowColor = AttrUtils.getColorFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSHADOWCOLOR, Color.TRANSPARENT.getValue());
        if (mTextShadowColor != Color.TRANSPARENT.getValue()) {
            mTextShadowRadius = AttrUtils.getFloatFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSHADOWRADIUS, 0);
            mTextShadowDistX = AttrUtils.getFloatFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSHADOWDISTX, 0);
            mTextShadowDistY = AttrUtils.getFloatFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_TEXTSHADOWDISTY, 0);
        }

        // BACKGROUND OFFSET
        mBackgroundOffset = AttrUtils.getDimensionFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_BACKGROUNDOFFSET, 0);
    }

    /**
     * Create font build font . builder
     *
     * @param context context
     * @param name    name
     * @return the font . builder
     */
    Font.Builder createFontBuild(Context context, String name) {
        ResourceManager resManager = context.getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry("resources/rawfile/" + name);
        Resource resource = null;
        try {
            resource = rawFileEntry.openRawFile();
        } catch (IOException e) {
            HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
        }
        StringBuffer fileName = new StringBuffer(name);
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName.toString());
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            int index;
            byte[] bytes = new byte[1024];
            while ((index = resource.read(bytes)) != -1) {
                outputStream.write(bytes, 0, index);
                outputStream.flush();
            }
        } catch (FileNotFoundException e) {
            HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
        } catch (IOException e) {
            HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
        } finally {
            try {
                resource.close();
                outputStream.close();
            } catch (IOException e) {
                HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
            }
        }
        Font.Builder builder = new Font.Builder(file);
        return builder;
    }

    /**
     * Init gradient colors *
     *
     * @param attrs attrs
     */
    private void initGradientColors(AttrSet attrs) {
        // PROGRESS GRADIENT TYPE
        mGradientType = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_GRADIENTTYPE, -1);
        if (mGradientType == -1) {
            return;
        }

        // ANGLE FOR LINEAR GRADIENT
        mGradientAngle = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_GRADIENTANGLE, (int) mStartAngle);

        // PROGRESS GRADIENT COLORS
        String gradientColors = AttrUtils.getStringFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_GRADIENTCOLORS, null);
        if (gradientColors != null) {
            String[] colors = gradientColors.split(",");
            mGradientColors = new Color[colors.length];
            try {
                for (int i = 0; i < colors.length; i++) {
                    mGradientColors[i] = new Color(RgbPalette.parse(colors[i]));
                }
            } catch (Exception e) {
                HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
            }
        }

        // PROGRESS GRADIENT COLORS'S DISTRIBUTIONS
        String gradientDist = AttrUtils.getStringFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_GRADIENTDISTRIBUTIONS, null);
        if (gradientDist != null) {
            String[] distributions = gradientDist.split(",");
            mGradientDistributions = new float[distributions.length];
            try {
                for (int i = 0; i < distributions.length; i++) {
                    mGradientDistributions[i] = Float.parseFloat(distributions[i].trim());
                }
            } catch (Exception e) {
                HiLog.error(new HiLogLabel(3, 0, "systembartint"), "%{public}s: %{public}s", BaseModeRenderer.class.getName(), e.getMessage());
            }
        }
    }

    /**
     * Setup
     */
    void setup() {
        mCircleBounds = new RectFloat();
        mBackgroundBounds = new RectFloat();
        mProvidedProgressColor = mProvidedBackgroundColor = mProvidedTextColor = -1;

        // BACKGROUND PAINT
        mBackgroundPaint = new Paint();
        mBackgroundPaint.setAntiAlias(true);
        mBackgroundPaint.setColor(new Color(mBackgroundColor));

        // PROGRESS PAINT
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setColor(new Color(mProgressColor));

        // TEXT PAINT
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(TextAlignment.CENTER);
        mTextPaint.setTextSize((int) mTextSize);
        mTextPaint.setColor(new Color(mTextColor));

        if (mTypeface != null) {
            mTextPaint.setFont(mTypeface.build());
        }
        if (mTextShadowColor != Color.TRANSPARENT.getValue()) {
            BlurDrawLooper textBlurDrawLooper = new BlurDrawLooper(mTextShadowRadius, mTextShadowDistX, mTextShadowDistY, new Color(mTextShadowColor));
            mTextPaint.setBlurDrawLooper(textBlurDrawLooper);
        }

        // TEXT LAYOUT
        defaultTextFormatter = progress -> (int) progress + "%";
        mTextEditor = mTextProgress + "";

        // ANIMATIONS
        mProgressAnimator = ValueAnimator.ofFloat(0, mProgress);
        mProgressAnimator.setDuration(mAnimDuration);
        mProgressAnimator.setInterpolator(mAnimInterpolator);
        mProgressAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mProgress = value;

                if (mProgress > 0 && mProgress <= 100) {
                    mTextProgress = (int) mProgress;
                } else if (mProgress > 100) {
                    mProgress = mTextProgress = 100;
                } else {
                    mProgress = mTextProgress = 0;
                }

                updateDrawingAngles();
                updateText();

                mView.onProgressUpdated(mProgress);
                mView.postInvalidateOnAnimation();
            }
        });
    }

    /**
     * Attach *
     *
     * @param view view
     */
    public void attach(IPercentageChartView view) {
        mView = view;
        setup();
    }

    /**
     * Measure *
     *
     * @param width         width
     * @param height        height
     * @param paddingLeft   padding left
     * @param paddingTop    padding top
     * @param paddingRight  padding right
     * @param paddingBottom padding bottom
     */
    public abstract void measure(int width, int height, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom);

    /**
     * Draw *
     *
     * @param canvas canvas
     */
    public abstract void draw(Canvas canvas);

    /**
     * Draw text *
     *
     * @param canvas canvas
     */
    void drawText(Canvas canvas) {
        float centerX = mCircleBounds.getCenter().getPointX();
        float centerY = mCircleBounds.getCenter().getPointY();
        int xPos = (int) centerX;
        int yPos = (int) (centerY - (mTextPaint.descent() + mTextPaint.ascent()) / 2);
        canvas.drawText(mTextPaint, mTextEditor, xPos, yPos);
    }

    /**
     * Destroy
     */
    public void destroy() {
        if (mProgressAnimator != null) {
            if (mProgressAnimator.isRunning()) {
                mProgressAnimator.cancel();
            }
            mProgressAnimator.cancel();
        }

        if (mProgressColorAnimator != null) {
            if (mProgressColorAnimator.isRunning()) {
                mProgressColorAnimator.cancel();
            }
            mProgressColorAnimator.cancel();
        }

        if (mBackgroundColorAnimator != null) {
            if (mBackgroundColorAnimator.isRunning()) {
                mBackgroundColorAnimator.cancel();
            }
            mBackgroundColorAnimator.cancel();
        }

        if (mTextColorAnimator != null) {
            if (mTextColorAnimator.isRunning()) {
                mTextColorAnimator.cancel();
            }
            mTextColorAnimator.cancel();
        }

        mProgressAnimator = mProgressColorAnimator = mBackgroundColorAnimator = mTextColorAnimator = null;
        mCircleBounds = mBackgroundBounds = null;
        mBackgroundPaint = mProgressPaint = mTextPaint = null;
        mGradientShader = null;
        mAdaptiveColorProvider = null;
        defaultTextFormatter = mProvidedTextFormatter = null;
    }

    /**
     * Update text
     */
    void updateText() {
        CharSequence text = (mProvidedTextFormatter != null) ?
                mProvidedTextFormatter.provideFormattedText(mTextProgress) :
                defaultTextFormatter.provideFormattedText(mTextProgress);
        mTextEditor = "";
        mTextEditor = mTextEditor + text;
    }

    /**
     * Update drawing angles
     */
    abstract void updateDrawingAngles();

    /**
     * Update provided colors *
     *
     * @param progress progress
     */
    void updateProvidedColors(float progress) {
        if (mAdaptiveColorProvider == null) {
            return;
        }
        int providedProgressColor = mAdaptiveColorProvider.provideProgressColor(progress);

        if (providedProgressColor != -1 && providedProgressColor != mProvidedProgressColor && mGradientType == -1) {
            mProvidedProgressColor = providedProgressColor;
            mProgressPaint.setColor(new Color(mProvidedProgressColor));
        }

        int providedBackgroundColor = mAdaptiveColorProvider.provideBackgroundColor(progress);
        if (providedBackgroundColor != -1 && providedBackgroundColor != mProvidedBackgroundColor) {
            mProvidedBackgroundColor = providedBackgroundColor;
            mBackgroundPaint.setColor(new Color(mProvidedBackgroundColor));
        }

        int providedTextColor = mAdaptiveColorProvider.provideTextColor(progress);

        if (providedTextColor != -1 && providedTextColor != mProvidedTextColor) {
            mProvidedTextColor = providedTextColor;
            mTextPaint.setColor(new Color(mProvidedTextColor));
        }
    }

    /**
     * Setup color animations
     */
    void setupColorAnimations() {
        if (mProgressColorAnimator == null) {
            mProgressColorAnimator = ValueAnimator.ofFloat(mProgressColor, mProvidedProgressColor);
            mProgressColorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    mProvidedProgressColor = (int) value;
                    mProgressPaint.setColor(new Color(mProvidedProgressColor));
                }
            });
            mProgressColorAnimator.setDuration(mAnimDuration);
        }

        if (mBackgroundColorAnimator == null) {
            mBackgroundColorAnimator = ValueAnimator.ofFloat(mBackgroundColor, mProvidedBackgroundColor);
            mBackgroundColorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    mProvidedBackgroundColor = (int) value;
                    mBackgroundPaint.setColor(new Color(mProvidedBackgroundColor));
                }
            });
            mBackgroundColorAnimator.setDuration(mAnimDuration);
        }

        if (mTextColorAnimator == null) {
            mTextColorAnimator = ValueAnimator.ofFloat(mTextColor, mProvidedTextColor);
            mTextColorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    mProvidedTextColor = (int) value;
                    mTextPaint.setColor(new Color(mProvidedTextColor));
                }
            });
            mTextColorAnimator.setDuration(mAnimDuration);
        }
    }

    /**
     * Update animations *
     *
     * @param progress progress
     */
    void updateAnimations(float progress) {
        mProgressAnimator.setFloatValues(mProgress, progress);
        mProgressAnimator.start();

        if (mAdaptiveColorProvider == null) {
            return;
        }

        int providedProgressColor = mAdaptiveColorProvider.provideProgressColor(progress);
        if (providedProgressColor != -1 && providedProgressColor != mProvidedProgressColor && mGradientType == -1) {
            mProvidedProgressColor = providedProgressColor;
            mProgressPaint.setColor(new Color(mProvidedProgressColor));
        }

        int providedBackgroundColor = mAdaptiveColorProvider.provideBackgroundColor(progress);
        if (providedBackgroundColor != -1 && providedBackgroundColor != mProvidedBackgroundColor) {
            mProvidedBackgroundColor = providedBackgroundColor;
            mBackgroundPaint.setColor(new Color(mProvidedBackgroundColor));
        }

        int providedTextColor = mAdaptiveColorProvider.provideTextColor(progress);
        if (providedTextColor != -1 && providedTextColor != mProvidedTextColor) {
            mProvidedTextColor = providedTextColor;
            mTextPaint.setColor(new Color(mProvidedTextColor));
        }
    }

    /**
     * Cancel animations
     */
    void cancelAnimations() {
        if (mProgressAnimator.isRunning()) {
            mProgressAnimator.cancel();
        }

        if (mProgressColorAnimator != null && mProgressColorAnimator.isRunning()) {
            mProgressColorAnimator.cancel();
        }

        if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
            mBackgroundColorAnimator.cancel();
        }

        if (mTextColorAnimator != null && mTextColorAnimator.isRunning()) {
            mTextColorAnimator.cancel();
        }
    }

    /**
     * Setup gradient colors *
     *
     * @param bounds bounds
     */
    abstract void setupGradientColors(RectFloat bounds);

    /**
     * Update gradient angle *
     *
     * @param angle angle
     */
    abstract void updateGradientAngle(float angle);


    /**
     * Set adaptive color provider *
     *
     * @param adaptiveColorProvider adaptive color provider
     */
    public abstract void setAdaptiveColorProvider(AdaptiveColorProvider adaptiveColorProvider);

    /**
     * Set text formatter *
     *
     * @param textFormatter text formatter
     */
    public void setTextFormatter(ProgressTextFormatter textFormatter) {
        this.mProvidedTextFormatter = textFormatter;
        updateText();
        mView.postInvalidate();
    }

    /**
     * Get progress float
     *
     * @return the float
     */
    public float getProgress() {
        return mProgress;
    }

    /**
     * Set progress *
     *
     * @param progress progress
     * @param animate  animate
     */
    public void setProgress(float progress, boolean animate) {
        if (this.mProgress == progress) {
            return;
        }

        cancelAnimations();

        if (!animate) {
            this.mProgress = progress;
            this.mTextProgress = (int) progress;

            updateProvidedColors(progress);
            updateDrawingAngles();
            updateText();

            mView.onProgressUpdated(mProgress);
            mView.postInvalidate();
            return;
        }

        updateAnimations(progress);
    }

    /**
     * Is draw background enabled boolean
     *
     * @return the boolean
     */
    public boolean isDrawBackgroundEnabled() {
        return mDrawBackground;
    }

    /**
     * Set draw background enabled *
     *
     * @param drawBackground draw background
     */
    public void setDrawBackgroundEnabled(boolean drawBackground) {
        if (this.mDrawBackground == drawBackground) {
            return;
        }
        this.mDrawBackground = drawBackground;
    }

    /**
     * Get start angle float
     *
     * @return the float
     */
    public float getStartAngle() {
        return mStartAngle;
    }

    /**
     * Set start angle *
     *
     * @param startAngle start angle
     */
    public abstract void setStartAngle(float startAngle);

    /**
     * Get background color int
     *
     * @return the int
     */
    public int getBackgroundColor() {
        if (!mDrawBackground) {
            return -1;
        }
        return mBackgroundColor;
    }

    /**
     * Set background color *
     *
     * @param backgroundColor background color
     */
    public void setBackgroundColor(int backgroundColor) {
        if ((mAdaptiveColorProvider != null && mAdaptiveColorProvider.provideBackgroundColor(mProgress) != -1) || this.mBackgroundColor == backgroundColor) {
            return;
        }
        this.mBackgroundColor = backgroundColor;
        if (!mDrawBackground) {
            return;
        }
        mBackgroundPaint.setColor(new Color(mBackgroundColor));
    }

    /**
     * Get progress color int
     *
     * @return the int
     */
    public int getProgressColor() {
        return mProgressColor;
    }

    /**
     * Set progress color *
     *
     * @param progressColor progress color
     */
    public void setProgressColor(int progressColor) {
        if ((mAdaptiveColorProvider != null && mAdaptiveColorProvider.provideProgressColor(mProgress) != -1) || this.mProgressColor == progressColor) {
            return;
        }

        this.mProgressColor = progressColor;
        mProgressPaint.setColor(new Color(progressColor));
    }

    /**
     * Get gradient type int
     *
     * @return the int
     */
    public int getGradientType() {
        return mGradientType;
    }

    /**
     * Set gradient colors *
     *
     * @param type      type
     * @param colors    colors
     * @param positions positions
     * @param angle     angle
     */
    public void setGradientColors(int type, Color[] colors, float[] positions, float angle) {
        mGradientType = type;
        mGradientColors = colors;
        mGradientDistributions = positions;
        setupGradientColors(mCircleBounds);
        if (mGradientType == GRADIENT_LINEAR && mGradientAngle != angle) {
            mGradientAngle = angle;
            updateGradientAngle(mGradientAngle);
        }
    }

    /**
     * Set gradient colors internal *
     *
     * @param type      type
     * @param colors    colors
     * @param positions positions
     * @param angle     angle
     */
    public void setGradientColorsInternal(int type, Color[] colors, float[] positions, float angle) {
        mGradientType = type;
        mGradientColors = colors;
        mGradientDistributions = positions;
        if (mGradientType == GRADIENT_LINEAR && mGradientAngle != angle) {
            mGradientAngle = angle;
        }
    }

    /**
     * Get gradient angle float
     *
     * @return the float
     */
    public float getGradientAngle() {
        return mGradientAngle;
    }

    /**
     * Get gradient colors color [ ]
     *
     * @return the color [ ]
     */
    public Color[] getGradientColors() {
        return mGradientColors;
    }

    /**
     * Get gradient distributions float [ ]
     *
     * @return the float [ ]
     */
    public float[] getGradientDistributions() {
        return mGradientDistributions;
    }

    /**
     * Get animation duration int
     *
     * @return the int
     */
    public int getAnimationDuration() {
        return mAnimDuration;
    }

    /**
     * Set animation duration *
     *
     * @param duration duration
     */
    public void setAnimationDuration(int duration) {
        if (this.mAnimDuration == duration) {
            return;
        }
        mAnimDuration = duration;
        mProgressAnimator.setDuration(mAnimDuration);
        if (mProgressColorAnimator != null) {
            mProgressColorAnimator.setDuration(mAnimDuration);
        }
        if (mBackgroundColorAnimator != null) {
            mBackgroundColorAnimator.setDuration(mAnimDuration);
        }
        if (mTextColorAnimator != null) {
            mTextColorAnimator.setDuration(mAnimDuration);
        }
        if (mBgBarColorAnimator != null) {
            mBgBarColorAnimator.setDuration(mAnimDuration);
        }
    }

    /**
     * Get animation interpolator int
     *
     * @return the int
     */
    public int getAnimationInterpolator() {
        return mProgressAnimator.getInterpolator();
    }

    /**
     * Set animation interpolator *
     *
     * @param interpolator interpolator
     */
    public void setAnimationInterpolator(int interpolator) {
        mProgressAnimator.setInterpolator(interpolator);
    }

    /**
     * Get text color int
     *
     * @return the int
     */
    public int getTextColor() {
        return mTextColor;
    }

    /**
     * Set text color *
     *
     * @param textColor text color
     */
    public void setTextColor(int textColor) {
        if ((mAdaptiveColorProvider != null && mAdaptiveColorProvider.provideTextColor(mProgress) != -1) || this.mTextColor == textColor) {
            return;
        }
        this.mTextColor = textColor;
        mTextPaint.setColor(new Color(textColor));
    }

    /**
     * Get text size float
     *
     * @return the float
     */
    public float getTextSize() {
        return mTextSize;
    }

    /**
     * Set text size *
     *
     * @param textSize text size
     */
    public void setTextSize(float textSize) {
        if (this.mTextSize == textSize) {
            return;
        }
        this.mTextSize = textSize;
        mTextPaint.setTextSize((int) textSize);
        updateText();
    }

    /**
     * Get typeface font . builder
     *
     * @return the font . builder
     */
    public Font.Builder getTypeface() {
        return mTypeface;
    }

    /**
     * Set typeface *
     *
     * @param typeface typeface
     */
    public void setTypeface(Font.Builder typeface) {
        if (this.mTypeface != null && this.mTypeface.equals(typeface)) {
            return;
        }
        mTypeface = typeface;
        switch (mTextStyle) {
            case NORMAL:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(false);
                break;
            case BOLD:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(false);
                break;
            case ITALIC:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(true);
                break;
            case BOLD_ITALIC:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(true);
                break;
        }
//        mTextPaint.getFont()
        mTextPaint.setFont(mTypeface.build());
        updateText();
    }

    /**
     * Get text style int
     *
     * @return the int
     */
    public int getTextStyle() {
        return mTextStyle;
    }

    /**
     * Set text style *
     *
     * @param mTextStyle m text style
     */
    public void setTextStyle(int mTextStyle) {
        if (this.mTextStyle == mTextStyle) {
            return;
        }
        this.mTextStyle = mTextStyle;
        switch (mTextStyle) {
            case NORMAL:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(false);
                break;
            case BOLD:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(false);
                break;
            case ITALIC:
                mTypeface.setWeight(Font.REGULAR);
                mTypeface.makeItalic(true);
                break;
            case BOLD_ITALIC:
                mTypeface.setWeight(Font.BOLD);
                mTypeface.makeItalic(true);
                break;
        }
        mTextPaint.setFont(mTypeface.build());
        updateText();
    }

    /**
     * Get text shadow color int
     *
     * @return the int
     */
    public int getTextShadowColor() {
        return mTextShadowColor;
    }

    /**
     * Get text shadow radius float
     *
     * @return the float
     */
    public float getTextShadowRadius() {
        return mTextShadowRadius;
    }

    /**
     * Get text shadow dist y float
     *
     * @return the float
     */
    public float getTextShadowDistY() {
        return mTextShadowDistY;
    }

    /**
     * Get text shadow dist x float
     *
     * @return the float
     */
    public float getTextShadowDistX() {
        return mTextShadowDistX;
    }

    /**
     * Set text shadow *
     *
     * @param shadowColor  shadow color
     * @param shadowRadius shadow radius
     * @param shadowDistX  shadow dist x
     * @param shadowDistY  shadow dist y
     */
    public void setTextShadow(int shadowColor, float shadowRadius, float shadowDistX, float shadowDistY) {
        if (this.mTextShadowColor == shadowColor
                && this.mTextShadowRadius == shadowRadius
                && this.mTextShadowDistX == shadowDistX
                && this.mTextShadowDistY == shadowDistY) {
            return;
        }
        this.mTextShadowColor = shadowColor;
        this.mTextShadowRadius = shadowRadius;
        this.mTextShadowDistX = shadowDistX;
        this.mTextShadowDistY = shadowDistY;

        BlurDrawLooper textBlurDrawLooper = new BlurDrawLooper(mTextShadowRadius, mTextShadowDistX, mTextShadowDistY, new Color(mTextShadowColor));
        mTextPaint.setBlurDrawLooper(textBlurDrawLooper);

        updateText();
    }
}